# --*--coding:utf-8--*--
import ctypes
import inspect
import os
import threading
import wx  # pip install wxpython==4.0.3  # 4.1.0以上版本不支持

# 全局变量
list_ths = []
list_ignore_path = []
text_choice = []

class MyFrame(wx.Frame):
    """
    MyFrame：一个基础的窗口框架类，作为父类给具体业务窗口去继承
    所有窗口共有的属性定义在这个基类里
    """
    session = {}  # 模拟Web的session，保留会话的数据

    def __init__(self, title, size=(1000, 700)):
        """
        一个基础窗口
        :param title: 窗口标题
        :param size: 窗口尺寸
        """
        super().__init__(parent=None, title=title, size=size,
                         style=wx.DEFAULT_FRAME_STYLE ^ wx.MAXIMIZE_BOX)

        # 设置窗口居中
        self.Center()

        # 设置Frame窗口内容面板
        self.panel = wx.Panel(parent=self)

        # 设置窗口图标
        # frame_icon_path = "favicon.ico"  # 窗口图标路径
        # ico = wx.Icon(frame_icon_path, wx.BITMAP_TYPE_ICO)
        # self.SetIcon(ico)

        '''# 设定窗口大小可调整范围
            # 这里设置了相同的最大和最小值，也就是固定了窗口大小。
            # 因为上面的窗口风格了保留了wx.RESIZE_BORDER，所以这里用另外一个放来来保证大小不可调整
            # 这样做有一点不好，就是鼠标放在窗口边缘，会变成调整窗口大小的样子，但是拉不动窗口
        self.SetSizeHints(size, size)
        '''
        # 绑定关闭按钮的点击事件
        self.Bind(wx.EVT_CLOSE, self.on_close)

    def on_close(self, event):
        """关闭窗口事件函数"""
        dlg = wx.MessageDialog(None, u'确定要关闭本窗口？', u'操作提示', wx.YES_NO | wx.ICON_QUESTION)
        if dlg.ShowModal() == wx.ID_YES:
            self.Destroy()

class HOMEPAGE(MyFrame):
    """继承了基类页面的主页类"""

    def __init__(self):
        """一个主窗口"""
        super().__init__(title='File Tree', size=(1200, 700))
        # 历史查询的路径信息
        path_tree = rf'{os.getenv("APPDATA")}' + '\\filetreeconf' + '\\'
        self.historical_path = path_tree + r"historical_path.txt"  # 配置文件路径
        if not os.path.exists(self.historical_path):  # 判断文件是否存在
            if not os.path.exists(path_tree):  # 判断文件夹是否存在
                os.makedirs(path_tree)

        """创建底部状态栏"""
        # self.CreateMyStatusBar()

        """创建路径输入框横向水平控件"""
        # 路径输入框
        path_label = wx.StaticText(self.panel, -1, label="路径：", pos=(10, 10), size=(30, -1), style=wx.ALIGN_RIGHT	)
        with open(self.historical_path, "a+") as f:  # historical_path.txt,存储10个历史查询的路径
            f.seek(0, 0)  # a+模式光标被放在文本末尾。seek到开头。
            list1 = f.read().split("\n")
            self.list1 = list1[len(list1) - 10 if len(list1) > 10 else 0:]
        self.path_contentpanel = wx.ComboBox(self.panel, -1, value='', choices=self.list1, style=wx.CB_SORT)
        # 路径输入框添加事件处理
        self.Bind(wx.EVT_COMBOBOX, self.path_contentpanel_method, self.path_contentpanel)
        # 路径输入框被选择事件
        self.Bind(wx.EVT_COMBOBOX, self.update_choice_button, self.path_contentpanel)
        # 后缀输入框
        suffix_label = wx.StaticText(self.panel, -1, label="展示后缀:", size=(30, -1), style=wx.ALIGN_RIGHT)
        # suffix_label.SetFont(wx.Font(11, wx.SWISS, wx.NORMAL, wx.NORMAL))
        self.suffix_contentpanel = wx.TextCtrl(self.panel, size=(30, -1), value=".*", style=wx.CB_SORT)
        # 创建选择路径、加载、保存按钮对象
        self.check_path_btn = wx.Button(parent=self.panel, size=(10, 10), label="选择路径")
        self.loading_btn = wx.Button(parent=self.panel, size=(1, -1), label="加载")
        self.loading_btn.SetDefault()  # 设为默认按钮，回车点击
        self.loading_btn.SetBackgroundColour("#00FFFF")  # 背景色
        self.suspend_btn = wx.Button(parent=self.panel, size=(1, -1), label="中止")
        self.reset_btn = wx.Button(parent=self.panel, size=(1, -1), label="重置")
        # 绑定加载、保存按钮事件：事件类型，绑定的事件，绑定的按钮
        self.Bind(wx.EVT_BUTTON, self.check_path_btn_onclick, self.check_path_btn)
        self.Bind(wx.EVT_BUTTON, self.loading_btn_onclick, self.loading_btn)
        self.Bind(wx.EVT_BUTTON, self.suspend_btn_onclick, self.suspend_btn)
        self.Bind(wx.EVT_BUTTON, self.reset_btn_onclick, self.reset_btn)

        """创建结果展示框横向水平控件"""
        # 创建结果展示窗口
        self.result_display = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE | wx.HSCROLL, size=(950, 600))
        self.Bind(wx.EVT_BUTTON, self.loading_btn_onclick, self.loading_btn)
        # 创建“不需要加载的文件夹”多选列表控件
        ignore_folder_label = wx.StaticText(self.panel, -1, label="不需要加载的文件夹：", pos=(0, 10), size=(30, -1), style=wx.ALIGN_LEFT)
        self.get_path_btn = wx.Button(parent=self.panel, size=(1, -1), label="GET")
        self.reset_path_btn = wx.Button(parent=self.panel, size=(1, -1), label="RESET")
        self.Bind(wx.EVT_BUTTON, self.update_choice_button, self.get_path_btn)
        self.Bind(wx.EVT_BUTTON, self.reset_path_btn_tools, self.reset_path_btn)
        self.ignore_folder_list = ['正直诚信', '专业敬业', '开放合作', '持续成长']
        self.ignore_folder_ListBox = wx.ListBox(self.panel, -1,  (20, 20), (60, 180), self.ignore_folder_list, wx.LB_MULTIPLE | wx.LB_SORT | wx.LB_HSCROLL | wx.LB_NEEDED_SB)
        self.suffix_contentpanel.SetHint('请输入需要展示的文件后缀类型，多个使用逗号隔开')
        # self.ignore_folder_ListBox.SetSelection()  # 默认选中
        self.Bind(wx.EVT_LISTBOX, self.on_choice, self.ignore_folder_ListBox)
        # 创建“每行结果前后拼接字符：”控件
        splicing_label = wx.StaticText(self.panel, -1, label="每行结果前后拼接字符：", pos=(0, 10), size=(30, -1), style=wx.ALIGN_LEFT)
        self.splicing_contentpanel_1 = wx.TextCtrl(self.panel, style=wx.CB_SORT)
        self.splicing_contentpanel_1.SetHint('请输入前缀，点击更新')
        self.splicing_contentpanel_2 = wx.TextCtrl(self.panel, style=wx.CB_SORT)
        self.splicing_contentpanel_2.SetHint('请输入后缀，点击更新')
        # self.splicing_contentpanel_1 = wx.TextCtrl(self.panel, value="请输入前缀，点击确定", style=wx.CB_SORT)
        # self.splicing_contentpanel_1.SetForegroundColour('#CDCDCD')
        # self.splicing_contentpanel_1.Bind(wx.EVT_LEFT_DOWN, self.splicing_method_1)
        # 创建“更新结果”按钮控件
        self.update_result_btn = wx.Button(parent=self.panel, size=(75, -1), label="更新结果")
        self.update_result_btn.SetBackgroundColour("#7fffd4")  # 绿色背景
        self.Bind(wx.EVT_BUTTON, self.update_result, self.update_result_btn)


        """开始面板上布局"""

        # 水平尺寸器：放路径输入框横向水平控件
        box = wx.BoxSizer()
        box.Add(path_label, proportion=1, flag=wx.EXPAND | wx.ALIGN_CENTER, border=1)
        box.Add(self.path_contentpanel, proportion=8, flag=wx.EXPAND | wx.ALIGN_LEFT, border=1)
        box.Add(self.check_path_btn, proportion=1, flag=wx.EXPAND | wx.ALIGN_LEFT, border=1)
        box.Add(suffix_label, proportion=1, flag=wx.RIGHT | wx.EXPAND | wx.ALIGN_LEFT, border=3)
        box.Add(self.suffix_contentpanel, proportion=1, flag=wx.RIGHT | wx.EXPAND | wx.LEFT | wx.ALIGN_LEFT, border=0)
        box.Add(self.loading_btn, proportion=2, flag=wx.LEFT | wx.ALIGN_LEFT, border=40)
        box.Add(self.suspend_btn, proportion=2, flag=wx.LEFT | wx.RIGHT | wx.ALIGN_LEFT, border=20)
        box.Add(self.reset_btn, proportion=2, flag=wx.RIGHT | wx.ALIGN_LEFT, border=40)
        # box.AddSpacer(1)  # 为 sizer 增加可拉伸的空间

        # 垂直尺寸器：放右侧区域控件
        v_box1 = wx.BoxSizer(wx.VERTICAL)

        box3 = wx.BoxSizer()
        box3.Add(self.get_path_btn, proportion=5, flag=wx.LEFT | wx.RIGHT | wx.ALIGN_LEFT, border=0)
        box3.Add(self.reset_path_btn, proportion=5, flag=wx.LEFT | wx.RIGHT | wx.ALIGN_LEFT, border=0)

        v_box1.Add(ignore_folder_label, proportion=0, flag=wx.ALIGN_LEFT | wx.EXPAND | wx.TOP, border=3)
        v_box1.Add(box3, proportion=0, flag=wx.ALIGN_RIGHT | wx.EXPAND | wx.TOP, border=3)
        v_box1.Add(self.ignore_folder_ListBox, proportion=0, flag=wx.ALIGN_RIGHT | wx.EXPAND | wx.TOP, border=3)
        v_box1.Add(splicing_label, proportion=0, flag=wx.ALIGN_RIGHT | wx.EXPAND | wx.TOP, border=20)
        v_box1.Add(self.splicing_contentpanel_1, proportion=0, flag=wx.ALIGN_RIGHT | wx.EXPAND | wx.TOP, border=3)
        v_box1.Add(self.splicing_contentpanel_2, proportion=0, flag=wx.ALIGN_RIGHT | wx.EXPAND | wx.TOP, border=3)
        v_box1.Add(self.update_result_btn, proportion=0, flag=wx.ALIGN_CENTER | wx.TOP, border=3)

        # 水平尺寸器：放结果展示框横向水平控件
        box2 = wx.BoxSizer()
        box2.Add(self.result_display, proportion=10, flag=wx.EXPAND | wx.LEFT, border=20)
        box2.Add(v_box1, proportion=2, flag=wx.ALIGN_RIGHT | wx.LEFT, border=2)

        # 垂直尺寸器：把一些横向Box或控件都装载其中，为全界面的尺寸器
        v_box = wx.BoxSizer(wx.VERTICAL)
        v_box.Add(box, proportion=0, flag=wx.ALIGN_LEFT | wx.EXPAND | wx.LEFT | wx.TOP, border=5)  # 随着窗口托大控件一直保持居中或居左
        v_box.Add(box2, proportion=0, flag=wx.ALIGN_LEFT | wx.EXPAND | wx.RIGHT | wx.TOP, border=20)  # 随着窗口托大控件一直保持居中或居左

        self.panel.SetSizer(v_box)  # 将窗口设置为指定布局器具有的尺寸
        # self.update_choice_button()
    '''
    flag 参数详解：
    wx.EXPAND|wx.ALL 会随着窗口变大而变化
    wx.ALIGN_CENTER  保持居中
    wx.ALIGN_LEFT  居左
    wx.ALIGN_RIGHT 居右
    wx.ALIGN_TOP  置顶
    wx.ALIGN_BOTTOM  置底部
    wx.ALIGN_CENTER_VERTICAL  垂直居中
    wx.ALIGN_CENTER_HORIZONTAL   水平居中
    wx.ALIGN_CENTER  居中
    '''
    def check_path_btn_onclick(self, event):
        """选择路径按钮函数"""
        dlg = wx.DirDialog(self, u"选择路径", style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            # print(dlg.GetPath())  # 文件夹路径
            self.path_contentpanel.SetValue(dlg.GetPath())
        dlg.Destroy()
        self.update_choice_button()

    def loading_btn_onclick(self, event):
        """加载按钮函数"""
        search_path = self.path_contentpanel.GetValue()  # 取出路径输入框的值
        # ignore_folder = []  # 取出忽略文件夹输入框的值，暂时不做此功能
        ignore_folder = text_choice
        filter_suffix = tuple(self.suffix_contentpanel.GetValue().replace('，', ',').replace(' ', '').split(","))  # 取出筛选后缀输入框的值写入元组，兼容中英文逗号
        if not os.path.isdir(search_path):
            self.showMsg(u"路径不存在", u"提示")
            return
        self.result_display.SetValue("")
        # 输出加载结果
        t2 = threading.Thread(
            target=self.search_path_method, args=(search_path, ignore_folder, filter_suffix, ))
        t2.daemon = True
        t2.start()
        list_ths.append(t2)
        # self.search_path_method(search_path, ignore_folder, filter_suffix)  # 输出加载结果

    def search_path_method(self, search_path, ignore_folder: list, filter_suffix):
        """
        根据search_path路径下的的所有后缀名为filter_suffix的文件，输出时筛选掉ignore_path目录下的文件
        :param search_path: 待搜索的路径。字符串
        :param ignore_folder:忽略的文件夹的绝对路径。列表
        :param filter_suffix:需要查出来的后缀名。元组
        :return:路径结果
        """
        for o, value in enumerate(ignore_folder):
            ignore_folder[o] = os.path.normcase(value.replace('\\\\', '\\'))
        list_file_path = ""
        if search_path:
            for root, dirs, files in os.walk(search_path):

                if os.path.normcase(root).replace('\\\\', '\\') in ignore_folder:  # 被忽略的文件夹路径
                    for i, j, k in os.walk(root):
                        pathsadads = os.path.normcase(str(i).replace('\\\\', '\\'))
                        ignore_folder.append(pathsadads)
                    continue
                list_all = ['', '.', ".*"]
                result = list(set(list_all).intersection(set(list(filter_suffix))))
                if not len(result):
                    files = [f for f in files if f.endswith(filter_suffix)]  # 选择后缀名
                for name in files:
                    list_file_path = os.path.join(root, name).replace('\\', '/') + '\n\n\n'  # 路径和文件名连接构成完整路径
                    list_file_path = list_file_path[:-1].replace('\n\n\n', '\n')
                    list_file_path = list_file_path[:-1].replace('\n\n', '\n')
                    # 展示结果
                    self.result_display.WriteText(list_file_path)
            # # 展示结果
            # self.result_display.SetValue(list_file_path)
            # 保存历史路径
            self.save_his_path(search_path)
        return list_file_path

    def save_his_path(self, search_path):
        """保存搜索过的路径到self.historical_path。路径存在且尚未保存过才保存,最多存十条数据"""
        with open(self.historical_path, "r") as f:  # historical_path.txt,存储10个历史查询的路径
            list1 = f.read().split("\n")
            Initial_list1 = list1
            list1 = list1[len(list1) - 10 if len(list1) > 10 else 0:]
        if search_path not in list1:
            if list1[0] == '':
                list1[0] = search_path
            else:
                list1.append(search_path)
            with open(self.historical_path, "w") as f:
                f.write("\n".join(list1[len(list1) - 10 if len(list1) > 10 else 0:]))
                if len(Initial_list1[0]):  # 如果下拉列表是空
                    self.path_contentpanel.Append(list1[-1])  # 追加到地址框下拉列表
                else:  # 写到第一个
                    self.path_contentpanel.Clear()
                    self.path_contentpanel.Append(list1[-1])
                    self.path_contentpanel.SetValue(list1[-1])

    def path_contentpanel_method(self, event):
        with open(self.historical_path, "r") as f:  # historical_path.txt,存储10个历史查询的路径
            list2 = f.read().split("\n")
            self.list2 = list2[len(list2) - 10 if len(list2) > 10 else 0:]
            # print("重新获取下拉列表1", self.list2)
        self.path_contentpanel.UpdateValue = self.list2

    def showMsg(self, msg, title):
        wx.MessageBox(msg, title, wx.OK | wx.ICON_INFORMATION)

    def on_choice(self, event):
        """获取多选列表被选中的选项"""
        global text_choice
        num_choice = self.ignore_folder_ListBox.GetSelections()
        text_choice = [self.ignore_folder_ListBox.GetString(i) for i in num_choice]
        print("多选列表被选中的选项", text_choice)
        return text_choice

    def reset_path_btn_tools(self, event):
        """忽略列表，取消选中"""
        global text_choice
        text_choice = []
        num_choice = self.ignore_folder_ListBox.GetSelections()
        for i in num_choice:
            self.ignore_folder_ListBox.Deselect(i)

    def update_choice_button(self, event=""):
        """更新子目录作为下拉列表的选项"""
        def _update_choice_button():
            search_path = self.path_contentpanel.GetValue()  # 取出路径输入框的值
            if not os.path.isdir(search_path):
                return
            if not os.path.exists(search_path):
                return -1
            self.ignore_folder_ListBox.Clear()
            for root, dirs, names in os.walk(search_path):
                list_ignore_path.append(root)
                self.ignore_folder_ListBox.Append(root)
        # 输出加载结果
        t3 = threading.Thread(target=_update_choice_button)
        t3.daemon = True
        t3.start()
        list_ths.append(t3)


    def splicing_method_1(self, event):
        """框底增加提示文字。找到替代方法，已弃用"""
        if self.splicing_contentpanel_1.GetValue() == '请输入前缀，点击确定':  # if the control isn't blank, clear it
            self.splicing_contentpanel_1.Clear()
            self.splicing_contentpanel_1.SetForegroundColour('#000000')
        self.splicing_contentpanel_1.Unbind(wx.EVT_LEFT_DOWN)
        self.splicing_contentpanel_1.SetFocus()

    def _async_raise(self, tid, exctype):
        """线程停止"""
        if not inspect.isclass(exctype):
            exctype = type(exctype)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
        if res == 0:
            raise ValueError("invalid thread id")
        elif res != 1:
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")

    def stop_thread(self, thread):
        """线程停止"""
        self._async_raise(thread.ident, SystemExit)

    def suspend_btn_onclick(self, event):
        """中止执行按钮事件的函数"""
        if not len(list_ths):
            return
        for key, value in enumerate(list_ths):
            print(key, value)
            try:
                self.stop_thread(value)
            except:
                pass
        list_ths.clear()

    def reset_btn_onclick(self, event):
        """重置按钮事件函数"""
        self.path_contentpanel.SetValue("")
        self.suffix_contentpanel.SetValue(".*")
        self.result_display.SetValue("")

        self.ignore_folder_ListBox.Clear()
        self.ignore_folder_ListBox.Append(self.ignore_folder_list)

    def CreateMyStatusBar(self):
        '''创建底部状态栏'''
        self.sb = self.CreateStatusBar()
        self.sb.SetFieldsCount(3)
        self.sb.SetStatusWidths([-2, -1, -1])
        self.sb.SetStatusStyles([wx.SB_RAISED, wx.SB_RAISED, wx.SB_RAISED])

        self.sb.SetStatusText(u'状态信息0', 0)
        self.sb.SetStatusText(u'', 1)
        self.sb.SetStatusText(u'状态信息2', 2)

    def update_result(self, evt):
        '''更新结果'''
        splicing_contentpanel_1 = self.splicing_contentpanel_1.GetValue()  # 取出前缀输入框的值
        splicing_contentpanel_2 = self.splicing_contentpanel_2.GetValue()  # 取出后缀输入框的值
        result = self.result_display.GetValue()

        if result[-1:] == "\n":
            result = result[:-1]
        if result:
            if splicing_contentpanel_1 and splicing_contentpanel_2 == "":  # 加前缀
                replace_str_1 = '\n' + splicing_contentpanel_1
                result_new_tmp = splicing_contentpanel_1 + result.replace('\n', replace_str_1)
                self.result_display.SetValue(result_new_tmp)  # 展示
            elif splicing_contentpanel_2 and splicing_contentpanel_1 == "":  # 加后缀
                replace_str_2 = splicing_contentpanel_2 + '\n'
                result_new = result.replace('\n', replace_str_2)+splicing_contentpanel_2
                self.result_display.SetValue(result_new)  # 展示
            elif splicing_contentpanel_1 and splicing_contentpanel_2:  # 加前缀和后缀
                replace_str_1 = '\n' + splicing_contentpanel_1
                result_new_tmp = splicing_contentpanel_1 + result.replace('\n', replace_str_1)
                replace_str_2 = splicing_contentpanel_2 + '\n'
                result_new = result_new_tmp.replace('\n', replace_str_2) + splicing_contentpanel_2
                self.result_display.SetValue(result_new)  # 展示
            else:
                self.showMsg(u"请先填入需更新内容", u"提示")
        else:
            self.showMsg(u"无需要处理的数据", u"提示")

    def delete_space_btn(self, event):
        pass


class App(wx.App):
    '''启动模块'''

    def OnInit(self):
        '''创建窗口对象'''
        '''在应用程序开始时到主事件循环开始前被自动调用，是wxpython所特有的一个方法。__OnInit__（）比__Init__()先被调用'''
        frame = HOMEPAGE()
        frame.Show()
        return True  # 只有当有至少一个参数并且返回一个True布尔值时才能正确执行程序

if __name__ == '__main__':
    app = App()  # 实例化
    app.MainLoop()  # 进入主事件循环